El dataset contiene 515k filas de reseñas de usuarios paa 1493 hoteles de lujo en Europa. Este dataset contiene los siguientes atributos:
-Hotel_Address: Dirección del Hotel -Review_Date: Fecha de cuando se realizo la Review. -Average_Score: Puntuación media del Hotel. -Hotel_Name: Nombre del hotel -Reviewer_Nationality: Nacionalidad del usuario que deja la reseña -Negative_Review: Comentarios negativos que el usuario ha dejado al hotel. Si no se pone ningun comentario negativo debe de ser: ‘No Negative’ -Review_Total_Negative_Word_Counts: Numero total de palabras en la review Negativa. -Positive_Review: Comentarios positivos que el usuario ha dejado al hotel. si no se pone ningun comentario positivo debe de ser: ‘No Positive’ -Review_Total_Positive_Word_Counts: Numero total de palabras en la review Positiva. -Reviewer_Score: Puntuacion que le ha dejado el usuario en la reseña al hotel. -Total_Number_of_Reviews_Reviewer_Has_Given: Numero de reseñas que ha hecho el usuario en el pasado. -Total_Number_of_Reviews: Numero total de reviews validas del hotel. -Tags: Etiquetas que el usuario le ha dejado al hotel. -days_since_review: Dias que han pasado desde que salio del hotel hasta que se hizo la reseña. -Additional_Number_of_Scoring:Hay algunos huespedes que hacen una puntuacion al servicio en vez de una reseña. Este numero indica cuantas puntuaciones son validad sin review. -lat: Latitud del hotel (geo) -lng: Longitud del hotel (geo)
library(sqldf)
library(plotly)
library(leaflet)
library(leaflet.extras)
library(ggplot2)
library(wordcloud2)
library(tm)
library(dplyr)
library(readr)
library(stringr)
library(broom)
library(udpipe)
library(tidytext)
library(tidyr)
library(gridExtra)
library(topicmodels)
df <- read_csv('./Datasets/Hotel_Reviews.csv')
#tratamos los datos para extraer el año de cada review en un nuevo atributo
df <- df %>%
mutate(Review_year = substr(as.character(Review_Date),
nchar(as.character(Review_Date))-3,
nchar(as.character(Review_Date))))
#Añade un nuevo atributo al data frame eliminando los caracteres que no sean un numero sobre la columna days_since_review => Ej: "3 days" -> 3
df <- mutate(df, days_since_review_t= as.numeric(sub('[^0-9]+',"",days_since_review )))
head(df)
## # A tibble: 6 x 19
## Hotel_Address Additional_Numb… Review_Date Average_Score Hotel_Name
## <chr> <dbl> <chr> <dbl> <chr>
## 1 s Gravesande… 194 8/3/2017 7.7 Hotel Are…
## 2 s Gravesande… 194 8/3/2017 7.7 Hotel Are…
## 3 s Gravesande… 194 7/31/2017 7.7 Hotel Are…
## 4 s Gravesande… 194 7/31/2017 7.7 Hotel Are…
## 5 s Gravesande… 194 7/24/2017 7.7 Hotel Are…
## 6 s Gravesande… 194 7/24/2017 7.7 Hotel Are…
## # … with 14 more variables: Reviewer_Nationality <chr>, Negative_Review <chr>,
## # Review_Total_Negative_Word_Counts <dbl>, Total_Number_of_Reviews <dbl>,
## # Positive_Review <chr>, Review_Total_Positive_Word_Counts <dbl>,
## # Total_Number_of_Reviews_Reviewer_Has_Given <dbl>, Reviewer_Score <dbl>,
## # Tags <chr>, days_since_review <chr>, lat <dbl>, lng <dbl>,
## # Review_year <chr>, days_since_review_t <dbl>
En la siguiente imagen se muestran los hoteles de lujo mas visitados por los turistas a partir de las reviews realizadas en las ciudades mas visitadas en el ranking anual (Barcelona, Paris, Londes…) El mapa permite hacer zoom para distinguir las zonas donde se encuentran los hoteles.
location <- df[!duplicated(df[,c('lat','lng','Hotel_Address')]),c('lat','lng','Hotel_Address','Average_Score')]
leaflet(data = location)%>%addProviderTiles(providers$Stamen.TonerLite)%>%addMarkers(popup = ~Hotel_Address,clusterOptions = markerClusterOptions())
El siguiete mapa muestra el mismo resultado que el anterior pero con el mapa a color, para la exportación a una posible aplicacion. Tambiién permite aumentar y disminuir el zoom.
leaflet(data = location)%>%addProviderTiles(providers$Esri.NatGeoWorldMap)%>%addMarkers(popup = ~Hotel_Address)
Estos atributos se calculan a partir del campo direccion del hotel “Hotel_Adress”, y se añade como nuevos atributos country y cities.
library(stringr)
df%>%select(Hotel_Name,lat,lng,Hotel_Address)%>%group_by(Hotel_Address)%>%filter(!duplicated(Hotel_Address))->hotel_details
hotel_details$country=sapply(str_split(hotel_details$Hotel_Address," "),function(x){x[length(x)]})
hotel_details$city=sapply(str_split(hotel_details$Hotel_Address," "),function(x){x[length(x)-1]})
## Remove the mention of "United" as "London" in the city column and "Kingdom" as "United Kingdom" in the country column
hotel_details$city=str_replace(hotel_details$city,"United","London")
hotel_details$country=str_replace(hotel_details$country,"Kingdom","United Kingdom")
df%>%left_join(hotel_details[,4:6],by = 'Hotel_Address')->df
countries=paste(unique(hotel_details$country),collapse=",")
message=paste("Los paises mencionados en el dataset son:", countries)
print(message)
[1] “Los paises mencionados en el dataset son: Netherlands,United Kingdom,France,Spain,Italy,Austria”
cities=paste(unique(hotel_details$city),collapse=",")
message=paste("Las ciudades mencionadas en el dataset son:", cities)
print(message)
[1] “Las ciudades mencionadas en el dataset son: Amsterdam,London,Paris,Barcelona,Milan,Vienna”
En la siguiente gráfica se puede apreciar el rango de puntuaciones media por paises, observando como Austria es el país que tendria las puntuaciones de media más altas.
df%>%ggplot(aes(x=as.factor(country),y=Average_Score))+geom_boxplot()+xlab("País")+ylab("Puntuación media")
Se añade al dataset el atributo turista, que representa si es un extranjero o no el usuario que puntúa.
En la siguiete gráfica observamos que los NO turistas son los que dejan una puntuación mayor que los que si lo son, a excepcion de Reino Unido.
ind<-which(is.na(df$Reviewer_Nationality))
#print(ind)
data_model<-df[-ind,]
#data_model<-df
#print(data_model$Reviewer_Nationality==data_model$country)
data_model$turista<-ifelse(data_model$Reviewer_Nationality==data_model$country,"Sí","No")
data_model$turista<-as.factor(data_model$turista)
#print(data_model)
data_model%>%group_by(country,turista)%>%summarise(average_score=mean(Average_Score))%>%ungroup()%>%mutate(average_score=average_score**7)%>%ggplot(aes(x=country,y=average_score,color=turista,fill=turista))+geom_bar(stat='identity',position='dodge')+xlab("País")+ylab("Puntuación media")+scale_y_continuous(breaks = NULL)
En la siguiente gráfica, enfocada a la ciudad de Barcelona, hemos elgido 5 países de diferentes continentes para observar cuales son los rangos de puntuaciones que le dan a la ciudad.
data_model%>%filter(city=='Barcelona',Reviewer_Nationality %in% c("United States of America","Australia","Ireland","United Arab Emirates","Saudi Arabia"))%>%ggplot(aes(x=Reviewer_Nationality,y=Average_Score))+geom_boxplot()+xlab("País")+ylab("Puntuación media")
En las dos gráficas que se muestran a continuación se observa la distribución que siguen las puntuaciones media que le dan los usuarios a los hoteles de Barcelona, dependiendo de si son de turistas o residentes locales, es decir, de nacionalidad española (locales) o no (turistas).
data_model%>%filter(city=='Barcelona',Reviewer_Nationality!='Spain')%>%ggplot(aes(x=Average_Score))+geom_histogram(alpha=0.3,color='blue',fill='blue')+xlab("Puntuación media")+ylab("Contador")+ggtitle("Distribución de puntos \n (Turistas)")+scale_x_continuous(limits = c(6,10))+theme(plot.title = element_text(size=16,hjust=0.5))->t
data_model%>%filter(city=='Barcelona',Reviewer_Nationality=='Spain')%>%ggplot(aes(x=Average_Score))+geom_histogram(alpha=0.3,color='red',fill='red')+xlab("Puntuación media")+ylab("Contador")+ggtitle("Distribución de puntos \n (Locales)")+scale_x_continuous(limits=c(6,10))+theme(plot.title = element_text(size=16,hjust=0.5))->l
grid.arrange(t,l,ncol=2)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
En la siguiente gráfica se muestran el número de usuarios que han dado una determinada puntuación. Si colocas el punturo del mouse por encima de las columnas se observa el número de usuarios que han dado dicha puntuación.
g <- ggplot(df[sample(nrow(df), 10000), ],aes(x=Reviewer_Score)) + geom_histogram(binwidth = 1)+theme_bw()+ggtitle('Distribución de las puntuaciones')+xlab("Puntuación del cliente")+ylab("Contador")
ggplotly(g)
A continuación, podemos observar las nacionalidades de los usuarios que dan las mayores puntuaciones medias, y las ordenamos de forma descendente para visualizar mejor el resultado. Si colocamos el puntero del mouse encima de la columna observaos el valor de dicha puntuación media.
avgscore_nation <- sqldf('SELECT Reviewer_Nationality, avg(Reviewer_Score) as avg_score from df group by Reviewer_Nationality order by avg(Reviewer_Score) desc')
avgscore_nation[166,1]<-'UnKnown'
g <- ggplot(avgscore_nation[1:20,],aes(x=reorder( Reviewer_Nationality, -avg_score),y=avg_score)) + geom_bar(stat = 'identity')+theme_bw()+ theme(axis.text.x = element_text(angle = 45, hjust = 1)) + ggtitle('TOP-20 de las nacionalidades con puntuación media más alta')+ylab("Puntuación media")+xlab("Nacionalidad")
ggplotly(g)
En cambio, en la siguiente gráfica, observamos justo lo contrario a la anterior. Ahora visualizamos las nacionalidaddes de usuarios que dan las peores puntuaciones medias. Ordenamos el resultado de forma ascendente para interpretar mejor el resultado. Si colocamos el puntero del mouse encima de la columna observaos el valor de dicha puntuación media.
g <- ggplot(avgscore_nation[207:227,],aes(x=reorder( Reviewer_Nationality, avg_score),y=avg_score)) + geom_bar(stat = 'identity')+theme_bw()+ theme(axis.text.x = element_text(angle = 45, hjust = 1)) + ggtitle('TOP-20 de las nacionalidades con las puntuaciones medias más bajas')+ylab("Puntuación media")+xlab("Nacionalidad")
ggplotly(g)
Muestra la correlacion existente entre la puntuacion y los dias que han pasado desde que el usuario abandonó el hotel y publicó la review.
#Añade una columa nueva a el data frame quitando los caracteres que no sean un numero sobre la columna data since review Ej: "3 days" -> 3
#df <- mutate(df, days_since_review_t= as.numeric(sub('[^0-9]+',"",days_since_review )))
#print(df)
g <- ggplot(df[sample(nrow(df), 10000), ],aes(x=days_since_review_t,y=Reviewer_Score)) + geom_point()+theme_bw()+geom_smooth(method = "lm")+ggtitle('Correlación entre la puntuación y los dias desde estancia')+ylab("Puntuación del cliente")+xlab("Días transcurridos desde estancia")
ggplotly(g)
A continuación calculamos la relacion existente entre las untuaciones medias que dan los usuarios y el numero de dias transcurridos desde que abandonaron el hotel. Agrupamos los dias desde que abandonaron el hotel por rangos. Se puede comprobar que no existe relacion distinguible entre las puntuaciones que dejan los usuarios y los dias transcurridos desde que abandonan el hotel. Todos dejan aproximadamente una media entre [8-9] puntos.
avgscore_days <- sqldf('SELECT rango_dias, avg(Reviewer_Score) as avg_score
from (
select case
when days_since_review_t between 0 and 50 then \'a) 0 a 50\'
when days_since_review_t between 51 and 100 then \'b) 51 a 100\'
when days_since_review_t between 101 and 150 then \'c) 101 a 150\'
when days_since_review_t between 151 and 200 then \'d) 151 a 200\'
when days_since_review_t between 201 and 250 then \'e) 201 a 250\'
when days_since_review_t between 251 and 300 then \'f) 251 a 300\'
when days_since_review_t between 301 and 350 then \'g) 301 a 350\'
when days_since_review_t between 351 and 400 then \'h) 351 a 400\'
else \'i) mas de 400\' end as rango_dias,
Reviewer_Score
from df)
group by rango_dias order by rango_dias desc')
g <- ggplot(avgscore_days,aes(x=rango_dias,y=avg_score)) + geom_bar(stat = 'identity')+theme_bw()+ theme(axis.text.x = element_text(angle = 45, hjust = 1)) + ggtitle('Relación entre las reviews y los dias transcurridos.')+ylab("Contador")
ggplotly(g)
### Relationship between frequency of review and score - codigo original
g <- ggplot(df[sample(nrow(df), 10000), ],aes(x=Total_Number_of_Reviews_Reviewer_Has_Given,y=Reviewer_Score)) + geom_point()+theme_bw()+geom_smooth(method = "lm")+ggtitle('Correlation between score and review frequency')
ggplotly(g)
En la siguiente grafica se muestran las palabras usadas con mas frecuencia en los comentarios positivos de las reviews. Problema: Aparece el articulo the. Más adelante lo eliminamos.
reviews <- df[sample(nrow(df), 40000), ]
reviews <- reviews[reviews$Positive_Review!='No Positive',]
reviews <- reviews[reviews$Negative_Review!='No Negative',]
term_freq <- function(df,sent){
if(sent=='pos'){
corpus <- Corpus(VectorSource(df$Positive_Review))
}else{
corpus <- Corpus(VectorSource(df$Negative_Review))
}
corpus <- tm_map(corpus, removeWords, stopwords("SMART"))
corpus <- tm_map(corpus, removeWords, stopwords("en"))
corpus <- tm_map(corpus, stripWhitespace)
dtm <-TermDocumentMatrix(corpus)
mat_dtm <- as.matrix(dtm)
v_dtm <- sort(rowSums(mat_dtm),decreasing = TRUE)
FreqMat <- data.frame(word = names(v_dtm), Freq = v_dtm)
FreqMat <- FreqMat[1:50,]
return(FreqMat)
}
wordcloud2(data = term_freq(reviews,'pos'),minRotation = 0,maxRotation = 0)
Se muestran las palabras mas frecuentes en las comentarios positivos de la review. No aparece el articulo the. Para eliminarlo buscamos en la primera columna del data.frame la posicion en la que se encuentra dicho articulo, y luego lo eliminamos.
reviews <- df[sample(nrow(df), 40000), ]
reviews <- reviews[reviews$Positive_Review!='No Positive',]
reviews <- reviews[reviews$Negative_Review!='No Negative',]
term_freq <- function(df,sent){
if(sent=='pos'){
corpus <- Corpus(VectorSource(df$Positive_Review))
}else{
corpus <- Corpus(VectorSource(df$Negative_Review))
}
corpus <- tm_map(corpus, removeWords, stopwords("SMART"))
corpus <- tm_map(corpus, removeWords, stopwords("en"))
corpus <- tm_map(corpus, stripWhitespace)
dtm <-TermDocumentMatrix(corpus)
mat_dtm <- as.matrix(dtm)
v_dtm <- sort(rowSums(mat_dtm),decreasing = TRUE)
FreqMat <- data.frame(word = names(v_dtm), Freq = v_dtm)
FreqMat <- FreqMat[1:50,]
return(FreqMat)
}
data = term_freq(reviews,'pos')
#print(data)
f <- data["the",]
#print(f)
indexIWant <- which(data[ , 1] == "the") # busco en la primera columna si existe la palabra the
#print(indexIWant)
#print(data[-c(indexIWant),]) # elimina la fila 3 -> the
data <- data[-c(indexIWant),]
#print(data)
wordcloud2(data,minRotation = 0,maxRotation = 0)
#######
# Al Exportar a HTML solo se muestra la primera de las graficas de wordcloud2, estas se pueden ver ejecutando el codigo dentro de Rstudio
#######
Es una gráfica similar a la anterior pero ahora se muestran las palabras mas frecuentes en las comentarios negativos. Eliminamos también el articulo the.
# Original
#wordcloud2(data = term_freq(reviews,'neg'),minRotation = 0,maxRotation = 0)
# Eliminado el articulo the
data = term_freq(reviews,'neg')
#print(data)
f <- data["the",]
#print(f)
indexIWant <- which(data[ , 1] == "the") # busco en la primera columna si existe la palabra the
#print(indexIWant)
#print(data[-c(indexIWant),]) # elimina la fila 3 -> the
data <- data[-c(indexIWant),]
#print(data)
wordcloud2(data,minRotation = 0,maxRotation = 0)
#######
# Al Exportar a HTML solo se muestra la primera de las graficas de wordcloud2, estas se pueden ver ejecutando el codigo dentro de Rstudio
#######
En la siguiente gráfica se muestra la distribucion que siguen las puntuaciones medias que dan los usuarios durante los años que se tienen en la reviews.
nationality <- df %>%
filter(Reviewer_Nationality != " ") %>%
select(Review_year,
Reviewer_Nationality,
Reviewer_Score,
Average_Score,
Total_Number_of_Reviews_Reviewer_Has_Given,
Total_Number_of_Reviews
) %>%
mutate(Reviewer_Nationality = as.character(Reviewer_Nationality)) %>%
group_by(Reviewer_Nationality,Review_year) %>%
summarise(
Averager_Score = mean(Average_Score),
Reviewer_Score = mean(Reviewer_Score),
Total_Number_of_Reviews_Reviewer_Has_Given = sum(Total_Number_of_Reviews_Reviewer_Has_Given),
Total_Number_of_Reviews = n()
) %>%
arrange(Reviewer_Nationality,Review_year)
g <- ggplot(nationality, aes(Averager_Score)) + scale_fill_brewer(palette = "Accent")+ylab("Contador")+xlab("Puntuación media")
g + geom_histogram(aes(fill=Review_year),
binwidth = .1,
col="black", #TODO poner en español
size=.1) + labs(title="Histograma - Distribución anual de las puntuaciones medias")